/*
This is part of Screenbar, which is based in part on the Litestep
shell source code.

Copyright (C) 2008-2009 Jim Babcock
Copyright (C) 1997-2009 The LiteStep Development Team

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/
#include "stdafx.h"

struct FlowElementState
{
	vector<vector<LayoutLocation>> groupContents;
};

/////////////////////////////////////////////////////////////////////////////

FlowElement::FlowElement(string prefix)
	:RectLayoutElement(prefix)
{
	parseChildTypes();
	
	for(unsigned ii=0; ii<childGroups.size(); ii++)
	{
		if(!childGroups[ii]->isConstant())
			childrenCanChange = true;
	}
	
	createAnimation = animationPool->getAnimation(getConfigString("CreateAnimation", "", prefix.c_str()));
	destroyAnimation = animationPool->getAnimation(getConfigString("DestroyAnimation", "", prefix.c_str()));
}

FlowElement::~FlowElement()
{
	for(unsigned ii=0; ii<childGroups.size(); ii++)
		delete childGroups[ii];
}

// A flow element has changed if:
//  - There is no previous list of content groups
//  - A non-const content group has gained or lost a member
//  - An animated element has finished its animation
bool FlowElement::changed(const LayoutCacheNode *node)
{
	FlowElementState *state = (FlowElementState*)node->elementData;
	if(!state)
		return true;
	
	int groupIndex = 0;
	for(unsigned ii=0; ii<childGroups.size(); ii++)
	{
		if(childGroups[ii]->isConstant())
			continue;
		vector<LayoutLocation> &group(state->groupContents[groupIndex]);
		
		vector<LayoutLocation> newChildren;
		ElementContext childBaseContext = getChildContext(&node->context);
		childGroups[ii]->getChildren(&childBaseContext, newChildren);
		
		if(group.size() != newChildren.size())
			return true;
		
		for(unsigned jj=0; jj<group.size(); jj++)
		{
			if(!group[jj].context.match(newChildren[jj].context))
				return true;
			
			AnimElement *animation = dynamic_cast<AnimElement*>(group[jj].element);
			if(animation && animation->isFinished())
				return true;
			
			if(group[jj].element != newChildren[jj].element)
				return true;
		}
		
		groupIndex++;
	}
	
	return false;
}

void FlowElement::update(LayoutCacheNode *node)
{
	bool changed = false;
	FlowElementState *state = (FlowElementState*)node->elementData;
	
	if(!state)
	{
		// If this is a new flow, skip all the animations and go straight to
		// the final state. (We have to skip animations here because otherwise
		// size-changing animations would interfere with the size-fallback
		// layout handling.)
		changed = true;
		state = new FlowElementState();
		node->elementData = state;
		
		for(unsigned ii=0; ii<childGroups.size(); ii++)
		{
			if(childGroups[ii]->isConstant())
				continue;
			
			vector<LayoutLocation> children;
			ElementContext childBaseContext = getChildContext(&node->context);
			childGroups[ii]->getChildren(&childBaseContext, children);
			
			state->groupContents.push_back(children);
		}
	}
	
	int groupIndex = 0;
	for(unsigned ii=0; ii<childGroups.size(); ii++)
	{
		if(childGroups[ii]->isConstant())
			continue;
		vector<LayoutLocation> &group(state->groupContents[groupIndex]);
		
		vector<LayoutLocation> newGroup;
		ElementContext childBaseContext = getChildContext(&node->context);
		childGroups[ii]->getChildren(&childBaseContext, newGroup);
		
		// 'group' represents the elements in this group as they currently are.
		// Some elements may have AnimElements (animations) standing in for
		// them.
		// 'newGroup' represents the elements in this group as they should be
		// after all animations have finished.
		//
		// At the end of this, 'group' should be the same as 'newGroup' except
		// that elements may be replaced by creation-animations, and there may
		// be extra deletion-animation elements. The only operations allowed
		// are resolving a finished animation, inserting a creation animation,
		// and replacing an element with a deletion animation.
		// (If there is no animation for creation or deletion, it's as though
		// the animation were set and then resolved immediately.)

		if(startAnimations(group, newGroup))
			changed = true;
		
		// Check for animations that have finished, and resolve them
		// (delete the animation element for a removal animation, replace with
		// the child element for an addition animation). Also, mark this node
		// as changed if it contains any pre-existing animations, since those
		// update every frame.
		if(resolveFinishedAnimations(group))
			changed = true;
		
		groupIndex++;
	}
}

void FlowElement::deleteElementData(LayoutCacheNode *node, void *elementData)
{
	FlowElementState *state = (FlowElementState*)node->elementData;
	delete state;
}

void FlowElement::getChildren(LayoutCacheNode *node,
	vector<LayoutLocation> &outChildren)
{
	FlowElementState *state = (FlowElementState*)node->elementData;
	if(!state) return;
	
	ElementContext childBaseContext = getChildContext(&node->context);
	
	int groupIndex = 0;
	for(unsigned ii=0; ii<childGroups.size(); ii++)
	{
		if(childGroups[ii]->isConstant())
		{
			childGroups[ii]->getChildren(&childBaseContext, outChildren);
		}
		else
		{
			vector<LayoutLocation> *cachedGroup = &state->groupContents[groupIndex];
			outChildren.insert(outChildren.end(), cachedGroup->begin(), cachedGroup->end());
			groupIndex++;
		}
	}
}

bool FlowElement::resolveFinishedAnimations(vector<LayoutLocation> &group)
{
	bool changed = false;
	
	for(vector<LayoutLocation>::iterator ii=group.begin(); ii!=group.end(); )
	{
		if(ii->element == NULL)
		{
			ii = group.erase(ii);
		}
		else
		{
			AnimElement *animation = dynamic_cast<AnimElement*>(ii->element);
			if(!animation) {
				ii++;
				continue;
			}
			
			changed = true;
			if(!animation->isFinished()) {
				ii++;
				continue;
			}
			
			int trigger = animation->getTrigger();
			
			switch(trigger)
			{
				case ANIM_CREATE:
					ii->element = animation->removeSubstitute();
					delete animation;
					if(!ii->element) {
						ii = group.erase(ii);
					} else {
						ii++;
					}
					break;
				case ANIM_DESTROY:
					delete animation;
					ii = group.erase(ii);
					break;
				default:
					assert(0);
					break;
			}
		}
	}
	
	return changed;
}

bool FlowElement::startAnimations(vector<LayoutLocation> &group,
	vector<LayoutLocation> &newGroup)
{
	bool changed = false;
	
	// Start from the left and match as far as possible. This takes
	// care of the common easy case where the lists are identical.
	int firstDifference = 0;
	for(; firstDifference<(int)group.size() && firstDifference<(int)newGroup.size(); firstDifference++)
	{
		if(!matchElements(newGroup[firstDifference], group[firstDifference]))
			break;
	}
	
	// Index newGroup, creating a mapping from elements in it->their offsets
	map<LayoutLocation, int> newSet;
	for(int ii=0; ii<(int)newGroup.size(); ii++)
		newSet[newGroup[ii]] = ii;
	
	// Turn any element which isn't in the new sequence into a deletion
	// animation
	for(int ii=firstDifference; ii<(int)group.size(); ii++)
	{
		if(isCreateAnimation(group[ii]))
		{
			LayoutLocation created = group[ii];
			AnimElement *animation = (AnimElement*)created.element;
			created.element = animation->removeSubstitute();
			
			// If an element is deleted before the creation animation
			// finishes, we have special case handling to do if the creation
			// and deletion animations are complementary.
			// TODO that special case.
			if(newSet.find(created) == newSet.end())
			{
				delete animation;
				group[ii].element = startDestroyAnimation(created);
			}
		}
		else if(newSet.find(group[ii]) == newSet.end())
		{
			if(destroyAnimation)
			{
				if(!isDestroyAnimation(group[ii])) {
					group[ii].element = startDestroyAnimation(group[ii]);
				}
			}
			else
			{
				group.erase(group.begin()+ii);
				ii--;
			}
		}
	}
	
	// Invariants at this point: all elements in group are either deletion
	// animations or are also in newGroup.
	
	// Put the sequence in the correct order. The common cases are that either
	// the sequence is already in order, or there is exactly one element out
	// of order. If there's one element out of order and it's moved by one, 
	// then there are two swapped elements and we can't tell which is moving
	// (animate its deletion and recreation) and which is staying in place,
	// so we need outside information.
	vector<int> indices;
	for(int ii=0; ii<firstDifference; ii++)
		indices.push_back(ii);
	for(unsigned ii=firstDifference; ii<group.size(); ii++)
	{
		if(isDestroyAnimation(group[ii])) {
			indices.push_back(-1);
		} else if(isCreateAnimation(group[ii])) {
			LayoutLocation loc = group[ii];
			loc.element = dynamic_cast<AnimElement*>(loc.element)->removeSubstitute();
			indices.push_back(newSet[loc]);
		} else {
			indices.push_back(newSet[group[ii]]);
		}
	}
	
	bool shuffled = false;
	int outOfSeq = -1;
	int prev = -1;
	int prev2 = -1;
	for(unsigned ii=1; ii<group.size(); ii++)
	{
		if(indices[ii] < 0)
			continue;
		
		if(indices[ii] < indices[ii-1])
		{
			if(outOfSeq >= 0) {
				shuffled = true;
				break;
			} else if(prev2 == -1) {
				outOfSeq = 0;
			} else {
				if(prev2 < indices[ii]) {
					outOfSeq = ii-1;
				} else {
					outOfSeq = ii;
				}
			}
		}
		
		prev2 = prev;
		prev = indices[ii];
	}
	
	if(shuffled)
	{
		// This can't be represented by simply moving one element. To keep the
		// panel from degrading into an excessive number of animations, cancel
		// all animations and fast forward to the final state.
		cancelAnimations(group);
		group = newGroup;
		return true;
	}
	else if(outOfSeq >= 0)
	{
		int correctIndex = newSet[group[outOfSeq]];
		LayoutLocation created = group[outOfSeq];
		created.element = startCreateAnimation(created);

		// Replace the out-of-sequence element with a deletion animation
		group[outOfSeq].element = startDestroyAnimation(group[outOfSeq]);

		// Find the correct place for this element
		bool inserted = false;
		for(unsigned ii=0; ii<group.size(); ii++)
		{
			if(ii == outOfSeq)
				continue;
			if(indices[ii] > correctIndex) {
				group.insert(group.begin()+ii, created);
				inserted = true;
				break;
			}
		}
		if(!inserted) {
			group.push_back(created);
		}
	}
	
	// If there's no destroy animation, null elements may have been inserted
	// as a substitute. Delete those.
	for(unsigned ii=0; ii<group.size(); )
	{
		if(group[ii].element == NULL)
			group.erase(group.begin()+ii);
		else
			ii++;
	}
	
	// Add any element which is in the new sequence but not the old one
	// Invariants at this point: all elements in group are either deletion
	// animations or are also in newGroup, and those elements which are in
	// newGroup are in the same order in both lists.
	int pos = 0;
	for(int ii=0; ii<(int)newGroup.size(); ii++)
	{
		while(pos<(int)group.size() && isDestroyAnimation(group[pos])) {
			pos++;
		}
		if(pos >= (int)group.size()) {
			LayoutLocation created = newGroup[ii];
			created.element = startCreateAnimation(created);
			group.push_back(created);
		} else {
			if(!matchElements(group[pos], newGroup[ii])) {
				LayoutLocation created = newGroup[ii];
				created.element = startCreateAnimation(created);
				group.insert(group.begin()+pos, created);
			}
			pos++;
		}
	}
	
	return changed;
}

void FlowElement::cancelAnimations(vector<LayoutLocation> &group)
{
	for(unsigned ii=0; ii<group.size(); ii++)
	{
		AnimElement *animation = dynamic_cast<AnimElement*>(group[ii].element);
		if(animation) {
			group[ii].element = animation->removeSubstitute();
			delete animation;
		}
	}
}

bool FlowElement::matchElements(LayoutLocation &canonical, LayoutLocation &actual)
{
	return canonical.context.match(actual.context);
}

bool FlowElement::isCreateAnimation(LayoutLocation &element)
{
	AnimElement *anim = dynamic_cast<AnimElement*>(element.element);
	return (anim && anim->getTrigger()==ANIM_CREATE);
}

bool FlowElement::isDestroyAnimation(LayoutLocation &element)
{
	AnimElement *anim = dynamic_cast<AnimElement*>(element.element);
	return (anim && anim->getTrigger()==ANIM_DESTROY);
}

LayoutElement *FlowElement::startCreateAnimation(LayoutLocation created)
{
	if(createAnimation && settings->enableAnimations && settings->enableLayoutAnimations) {
		return new AnimElement(ANIM_CREATE, created, createAnimation);
	} else {
		return created.element;
	}
}

LayoutElement *FlowElement::startDestroyAnimation(LayoutLocation destroyed)
{
	if(destroyAnimation && settings->enableAnimations && settings->enableLayoutAnimations) {
		return new AnimElement(ANIM_DESTROY, destroyed, destroyAnimation);
	} else {
		return NULL;
	}
}

